#include <QDataStream>
#include <QMessageBox>
#include "Networking/session.h"
#include "Networking/server.h"
#include "Networking/documentmanager.h"
#include "Networking/chatmanager.h"
#include "Networking/usermanager.h"
#include "Change/message.h"
#include "Change/textchange.h"
#include "WelcomeScreen/welcomescreen.h"

const int Session::timeout = 1200;

Session::Session()
{
	documentManager = new DocumentManager(this);
	userManager = new UserManager(this);
	chatManager = new ChatManager(this);

	connection = NULL;
	server = NULL;

	currentMessageSize = 0;
}

/**
  * Connects to a server
  * @return If the connection was successsfully made
  */
bool Session::connectToServer(QString address, int port)
{
	connection = new QTcpSocket(this);
	connection->connectToHost(address, port);

	qDebug("Client attempting to connect to %s:%d", address.toAscii().data(), port);

	if (!connection->isValid())
		return false;

	//do a handshake if we are NOT the server
	if (!isServer())
	{
		//Receive the session info
		QByteArray msg = getMessageBlocking();

		if (msg.isEmpty())
			return false;

		QDataStream sin(msg);
		sin >> sessionName;

		qDebug("Client recieved session name: %s",sessionName.toAscii().data());


		//Receive the user info
		QList<UserMessage> usrMessages;

		do
		{
			msg = getMessageBlocking();

			if (msg.isEmpty())
				return false;

			if (Message::getOpType(msg) != Message::USER_INFO)
				break;

			usrMessages.append(UserMessage(msg));
		} while (connection->bytesAvailable() > 0);

		userManager->userServerHandshake(usrMessages);

		if (Message::getOpType(msg) != Message::USER_INFO)	//if there is a leftover message handle it appropriately
			handleMessage(msg);

		recieveMessage();	//get the rest of the messagess (because they will not have triggered readyRead!)
	}

	connect(connection, SIGNAL(readyRead()), this, SLOT(recieveMessage()));	//connect the signals now all is well

	return true;
}

/**
  *	Create a server object, and connect to it
  */
void Session::runAsServer(QString newSessionName, int port)
{
	sessionName = newSessionName;

	server = new Server(sessionName, port, this);
	server->start();
	server->moveToThread(server);	//push the server away... we don't want it anymore :)

	connectToServer("127.0.0.1", port);
}

void Session::sendMessage(QByteArray data)
{
	if (connection != NULL)
	{
		QDataStream dout(connection);
		dout << (qint64)data.size();	//write the size of the message

		connection->write(data);		//write the message
	}
}

/**
  * This function is called everytime a message is received from the server.
  * It determines who the message is intended for, and sends it to the appropriate handler
  */
void Session::recieveMessage()
{
	while (connection->bytesAvailable() > 0)
	{
		QByteArray msg;

		QDataStream din(connection);

		if (currentMessageSize == 0)
		{
			if (connection->bytesAvailable() < qint64(sizeof(qint64)))
				return;

			din >> currentMessageSize;
		}

		if (connection->bytesAvailable() < currentMessageSize)
			return;

		handleMessage(connection->read(currentMessageSize));

		currentMessageSize = 0;
	}
}


/**
  * Sends a message to the appropriate handler
  */
void Session::handleMessage(QByteArray msg)
{
	quint8 operation = Message::getOpType(msg);

	//Filter messages through if statements and send to appropriate places
	if (operation == Message::USER_INFO)
	{
		userManager->processUserMessage(UserMessage(msg));
	}
	else if (operation == Message::INITDOC)
	{
		msg.remove(0, sizeof(quint8));    //Remove the front op

		this->loadSession(msg);
	}
	else if (operation == Message::CHAT)
	{
		chatManager->receiveMsg(msg);
	}
	else if (operation == Message::SYS)
	{
		chatManager->receiveSys(msg);
	}
	else
	{
		//send the change to the document manager if it is not needed elsewhere
		documentManager->receiveChange(msg);
	}
}


/**
  * This function is used ONLY while handshaking with the server
  * It gets a SINGLE message and returns it in a byte array
  * This FUNCTION IS A BLOCKING FUNCTION and will freeze the GUI.
  * This is acceptable as a process bar showing the connection progress
  * can be displayed while this happens. (Incremently check if user cancels)
  */
QByteArray Session::getMessageBlocking()
{
	QDataStream din(connection);
	qint64 msgSize;

	while (connection->bytesAvailable() < qint64(sizeof(qint64)))
	{
		if (!connection->waitForReadyRead(timeout))
		{
			return QByteArray();
		}
	}

	din >> msgSize;

	while (connection->bytesAvailable() < qint64(sizeof(msgSize)))
	{
		if (!connection->waitForReadyRead(timeout))
			return QByteArray();
	}

	//get the session name from the data
	QByteArray msg;
	msg = connection->read(msgSize);

	return msg;
}

void Session::saveSession(QString filename)
{
    //Call next functions
    documentManager->saveSession(filename);
    userManager->saveSession(filename);
    //chatManager->saveSession(filename);       //Not implemented yet

    WelcomeScreen::getInstance()->addRecentSession(sessionName, filename + ".weave");
}

void Session::saveSession(QByteArray& myBytes)
{
    //Call next functions
    documentManager->saveSession(myBytes);	//Returns the file metadata, then file user statistic pairs separated by a group separator (0x1D)

}

void Session::loadSession(QString filename)
{
    //Call next functions
    userManager->loadSession(filename);        //Load up previous users, set to offline
    documentManager->loadSession(filename);    //Load in documents

    //Chat manager
    QString msg = " has resumed the session.";
    this->getChatManager()->sendSys(msg);

    WelcomeScreen::getInstance()->addRecentSession(sessionName, filename + ".weave");
}

void Session::loadSession(QByteArray& myBytes)	//Loads in the documents. Users have already been sent.
{
    documentManager->loadSession(myBytes);

}
